package P2PConnection;

import Commands.Command;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.locks.Lock;

/**
 * Created with IntelliJ IDEA.
 * Authors: Henrique Alberto Rusa and Danilo Barbosa Porto
 * Date: 04/11/13
 *
 * This java class presents the Socket server side of the application.
 * Here we will be using a singleton implementation because our server will listen on one connection port, creating a
 * ClientHandler (Nested Class) to each connection accepted by our application.
 */

public class SocketServer {

    /*
     * Reference to the lock parameter of the instructionLine of the ConnectionHandler class
     */
    private Lock lock;

    /*
     * Parameter to set a portNumber to the server listen to, so it will not need to set everytime we use it
     */
    final private static int portNumber = 2314;

    /*
     * This will be a private static because we utilize a singleton implementation where no one can
     * access through the class the instance.
     */
    private static SocketServer instance;

    /*
     * Socket where the server will be listening
     */
    private ServerSocket serverSocket;

    /*
     * List to reference the instruction queue from the connection layer (ConnectionHandler).
     */
    private List<Command> buffer;

    /*
     * We will allocate the instance parameters in the constructor, initializing it and we will put it on private so
     * the user has to call the getInstance method to return the allocated object.
     */
    private SocketServer(List<Command> instructionLine, Lock lock) {
        serverSocket = null;
        this.buffer = instructionLine;
        this.lock = lock;
    }

    /*
     * Method getInstance that returns the instance of the SocketServer object.
     * It is implemented as a synchronized method to not let the singleton failed it´s logic, when on a multi-threaded
     * environment that would cause serious errors while executing the if and else statement.
     */
    public static synchronized SocketServer getInstance(List<Command> instructionLine,Lock lock) {
        if(instance == null){
            instance = new SocketServer(instructionLine,lock);
        }
        return instance;
    }

    /*
     * Method to return all the commands from the buffer line, to add it up to the instructionLine of the
     * ConnectionHandler
     */
    public List<Command> returnInstructions(){
        return buffer.subList(0, buffer.size());
    }

    /*
     *  Method to start the Server.
     *  This will put the socket listening on the portNumber indicated on the definition above.
     *  First, it will allocate a server and associate it to the portNumber. Then keep the instance listening on the
     *  port with the handleClientRequest.
     */
    public void startServer() {
        try {
            serverSocket = new ServerSocket(portNumber);

            // keep receiving and listening on the socket
            while (true){
                this.handleClientRequest(buffer);
            }
        } catch (IOException e) {
            // TODO
        }
    }

    /*
     *  Method to handle the connection accepted by the server to the SocketClientHandler.
     *  It will just instantiate a new object to manage the request and if it does not work, will write a log error
     *  to the file error log.
     */
    private void handleClientRequest(List<Command> rootBuffer) {
        try {
            new Thread(new SocketClientHandler(this.serverSocket.accept(),rootBuffer, lock)).start();
        } catch (IOException e) {
            // TODO
            // put on log error (date, time, error)
        }
    }

    /*
     *  Nested class that handles the connections made to the SocketServer.
     *  This object will be instantiated and initialized by the SocketServer only when it gets a request for connection,
     *  just to put the server listening to different requests over the port.
     */
    private class SocketClientHandler implements Runnable{

        /*
         * Socket where the server will accept the connection to this handler
         */
        private Socket socket;

        /*
         * Lock parameter to block the critical region of the instruction list that is used
         */
        private Lock lock;

        /*
         * Reference to the list of the connectionHandler, to save all the commands received from the socket
         */
        private List<Command> list;

        /*
         *  Object Stream where the handler will receive objects through the InputStream associated to this Object
         */
        private ObjectInputStream _in;

        /*
         *  Object of the type instruction where the server will receive information through the connections made to
         *  the socket.
         */
        private Command commandRequest;

        /*
         * Constructor of class where it instantiates the object input and output. This method associates
         * the socket where the server accepted the connection and the socket of the handler.
         */
        public SocketClientHandler(Socket socket, List<Command> rootBuffer, Lock lockInner) throws IOException {
            this.lock = lockInner;
            this.socket = socket;

                _in = new ObjectInputStream(socket.getInputStream());
                list = rootBuffer;
        }

        /*
         * Method addOnList is synchronized using a lock, referred to with the parameter on the object. This has to be
         * just because everyone needs to lock the same object to keep the synchronization
         */
        private void addOnList(Command in){
            // try to lock
            this.lock.lock();
            try{
                list.add(in);
            } finally {
                // unlock when finished
                this.lock.unlock();
            }
        }

        /*
         * Method run of the thread created to handle the server connection.
         * This method will receive a command of the socket and close its parameters of data transfer when done.
         */
        @Override
        public void run() {
            try {

                // Receive object instruction through socket
                commandRequest = (Command )_in.readObject();
                this.addOnList(commandRequest);
                // Close instances
                _in.close();

            } catch (IOException e) {
                //TODO
            } catch (ClassNotFoundException e) {
                //TODO
            }
        }
    }
}
































